/**
 * <pre>
   Copyright 2016 aaron.sd

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 * </pre>
 * 
 * @author aaronwong.sd@foxmail.com
 * @version 上午10:23:55
 */
package com.iwechat.service.queue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.springframework.stereotype.Component;

import com.iwechat.jedis.RedisUtil;
import com.iwechat.mybatis.generator.model.TemplateMessage;
import com.iwechat.service.TempMsgPriorityQueue;

/**
 * 基于Redis的消息队列
 * 
 * @author aaronwong.sd@foxmail.com
 */
@Component("RedisPriorityQuenue")
public class RedisPriorityQuenue implements TempMsgPriorityQueue {
    public static final String[] quenueNames = new String[10];
    static {
        for (int i = 0; i < quenueNames.length; i++) {
            // 9级最高,0级最低
            quenueNames[i] = "TemplateMessageQuenue-Level" + i;
        }
    }

    public static int randomLevel() {
        int level = 9;
        double random = Math.random() * 55;
        if (random > 45) {
            level = 9;
        } else if (random > 36) {
            level = 8;
        } else if (random > 28) {
            level = 7;
        } else if (random > 21) {
            level = 6;
        } else if (random > 15) {
            level = 5;
        } else if (random > 10) {
            level = 4;
        } else if (random > 6) {
            level = 3;
        } else if (random > 3) {
            level = 2;
        } else if (random > 1) {
            level = 1;
        } else {
            level = 0;
        }
        return level;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.iwechat.service.TempMsgPriorityQueue#offer(com.iwechat.mybatis.generator.model.TemplateMessage)
     */
    @Override
    public boolean offer(TemplateMessage m) {
        int level = m.getPriority();
        level = level % 10;
        String quenueName = quenueNames[level];
        // 队尾入列
        RedisUtil.rpush(quenueName.getBytes(), objectToBytes(m));
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.iwechat.service.TempMsgPriorityQueue#take()
     */
    @Override
    public TemplateMessage take() {
        TemplateMessage m = null;
        while (m == null) {
            m = takeRandom();
        }
        return m;
    }

    /**
     * 按权值随机从一个队列获取一条消息
     * 
     * @return
     */
    private TemplateMessage takeRandom() {
        String quenueName = quenueNames[randomLevel()];
        byte[] b = RedisUtil.lpop(quenueName.getBytes());
        if (b != null && !"nil".equalsIgnoreCase((new String(b)))) {
            return (TemplateMessage) bytesToObject(b);
        } else {
            return null;
        }
    }

    /**
     * 从指定优先级队列取消息
     * 
     * @param level
     * @return
     */
    public TemplateMessage take(int level) {
        level = level % 10;
        String quenueName = quenueNames[level];
        // 队首出列
        byte[] b = RedisUtil.lpop(quenueName.getBytes());
        if (b == null || "nil".equalsIgnoreCase(new String(b))) {
            return null;
        } else {
            return (TemplateMessage) bytesToObject(b);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.iwechat.service.TempMsgPriorityQueue#monitor()
     */
    @Override
    public String monitor() {
        StringBuffer buffer = new StringBuffer(256);
        buffer.append(this);
        buffer.append("\n");
        long sum = 0, len = 0;
        for (int i = 0; i < quenueNames.length; i++) {
            buffer.append(quenueNames[i]);
            buffer.append("'s size is ");
            len = RedisUtil.llen(quenueNames[i].getBytes());
            buffer.append(len);
            buffer.append("\n");
            sum += len;
        }
        buffer.append("summation: ");
        buffer.append(sum);
        return buffer.toString();
    }

    /**
     * 无意义
     */
    @Override
    public int getFreeSize() {
        return 102400;
    }

    /**
     * 无意义
     */
    @Override
    public int getFreePercent() {
        return 100;
    }

    /**
     * 对象转byte[]
     * 
     * @param obj
     * @return
     * @throws IOException
     */
    public static byte[] objectToBytes(Object obj) {
        byte[] bytes = null;
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = null;
        try {
            oo = new ObjectOutputStream(bo);
            oo.writeObject(obj);
            bytes = bo.toByteArray();
        } catch (IOException e) {
        } finally {
            try {
                bo.close();
            } catch (IOException e) {
            }
            try {
                if (oo != null) {
                    oo.close();
                }
            } catch (IOException e) {
            }
        }
        return bytes;
    }

    /**
     * byte[]转对象
     * 
     * @param bytes
     * @return
     * @throws Exception
     */
    public static Object bytesToObject(byte[] bytes) {
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream sIn = null;
        Object ret = null;
        try {
            sIn = new ObjectInputStream(in);
            ret = sIn.readObject();
        } catch (Exception e) {
        } finally {
            try {
                in.close();
            } catch (IOException e) {
            }
            try {
                if (sIn != null) {
                    sIn.close();
                }
            } catch (IOException e) {
            }
        }
        return ret;
    }
}
